package rmi.server;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;

import javax.swing.Timer;

import model.BetContainer;
import model.CardInterface;
import model.ChosenAction;
import model.GameStake;
import model.GameType;
import model.GameVariant;
import model.HoldEmGame;
import model.Participant;
import model.PlayerGameStatsInterface;
import model.PlayerInterface;
import model.UserInterface;
import rmi.client.Client;
import toolkit.Debug;
import data.GameInterface;
import data.Seat;
import data.SeatInterface;
import data.Session;
import data.SessionInterface;
import exception.GameException;
import exception.LoginException;

public class ServerRemote extends Remote implements Server
{
	private HashMap<Integer, model.Game> games = new HashMap<Integer, model.Game>();
	
	private HashMap<Integer, Client> clients = new HashMap<Integer, Client>();
	
	private model.Game defaultGame;
	
	private HashMap<Integer, Session> sessions = new HashMap<Integer, Session>();
	
	private static int[] stakes = new int[] {1, 2, 3, 5, 10};
	
	private static int[] seats = new int[] {2, 4, 6, 8, 10};
	
	private static EnumMap<GameVariant, EnumMap<GameType, EnumMap<GameStake, HashMap<Integer, HashMap<Integer, Boolean>>>>> gameLookup = new EnumMap<GameVariant, EnumMap<GameType, EnumMap<GameStake, HashMap<Integer, HashMap<Integer, Boolean>>>>>(GameVariant.class);
	
	static
	{
		for (GameVariant gameVariant: GameVariant.values())
		{
			EnumMap<GameType, EnumMap<GameStake, HashMap<Integer, HashMap<Integer, Boolean>>>> gameVariants = new EnumMap<GameType, EnumMap<GameStake, HashMap<Integer, HashMap<Integer, Boolean>>>>(GameType.class);
			
			gameLookup.put(gameVariant, gameVariants);
			
			for (GameType gameType: GameType.values())
			{
				EnumMap<GameStake, HashMap<Integer, HashMap<Integer, Boolean>>> gameTypes = new EnumMap<GameStake, HashMap<Integer, HashMap<Integer, Boolean>>>(GameStake.class);
				
				gameVariants.put(gameType, gameTypes);
				
				for (GameStake gameStake: GameStake.values())
				{
					HashMap<Integer, HashMap<Integer, Boolean>> gameStakes = new HashMap<Integer, HashMap<Integer, Boolean>>();
					
					gameTypes.put(gameStake, gameStakes);
					
					for (int i = 0; i < 4; i++)
					{
						HashMap<Integer, Boolean> betSizes = new HashMap<Integer, Boolean>();
						
						gameStakes.put(i, betSizes);
						
						for (int n = 0; n < 4; n++)
						{
							betSizes.put(n, false);
						}
					}
				}
			}
		}
	}
	
	private static ServerRemote instance;
	
	public static ServerRemote getInstance()
	{
		if (instance == null)
		{
			instance = new ServerRemote();
		}
		
		return instance;
	}
	
	private ServerRemote()
	{
		updateGameList();
		
		new Timer(60 * 1000, new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				updateGameList();
			}
		})
		.start();
	}
	
	final protected Object clone()
	{
		return null;
	}
	
	public Session getSession(int gameId)
	{
		return sessions.get(gameId);
	}
	
	public void updateGameList()
	{
		for (GameVariant gameVariant: GameVariant.values())
		{
			for (GameType gameType: GameType.values())
			{
				for (GameStake gameStake: GameStake.values())
				{
					for (int i = 0; i < 4; i++)
					{
						for (int n = 0; n < 4; n++)
						{
							if (!gameLookup.get(gameVariant).get(gameType).get(gameStake).get(i).get(n))
							{
								if (gameVariant == GameVariant.HOLD_EM && gameType == GameType.SINGLE_TABLE)
								{
									HoldEmGame game = null;
									
									try
									{
										game = new HoldEmGame(gameType, gameStake, stakes[i], seats[n]);
										
										if (gameType == GameType.SINGLE_TABLE)
										{
											game.setBuyin(stakes[i] * 200);
										}
										
										game.setServerRemote(this);
										game.open();
									}
									catch (GameException e)
									{
										e.printStackTrace();
									}
									
									games.put(game.getId(), game);
									
									sessions.put(game.getId(), new Session());
									
									gameLookup.get(gameVariant).get(gameType).get(gameStake).get(i).put(n, true);
								}
							}
						}
					}
				}
			}
		}
	}
	
	public ArrayList<GameInterface> getGameList()
	{
		ArrayList<GameInterface> gameList = new ArrayList<GameInterface>();
		
		for (model.Game game: games.values())
		{
			gameList.add(new data.Game(game));
		}
		
		return gameList;
	}
	
	public GameInterface getDefaultGame()
	{
		data.Game gameData = null;
		
		if (defaultGame == null)
		{
			try
			{
				defaultGame = new HoldEmGame(GameType.SINGLE_TABLE, GameStake.NO_LIMIT, 1, 2);
				defaultGame.setServerRemote(this);
				defaultGame.setBuyin(10);
				defaultGame.open();
				
				games.put(defaultGame.getId(), defaultGame);
				
				sessions.put(defaultGame.getId(), new Session());
			}
			catch (GameException e)
			{
				Debug.log(e);
			}
		}
		
		gameData = new data.Game(defaultGame);
		
		return gameData;
	}
	
	public void addClient(Client client) throws LoginException
	{
		try
		{
			Integer userId = client.getUser().getId();
			
			if (clients.get(userId) != null)
			{
				throw new LoginException();
			}
			
			clients.put(userId, client);
		}
		catch (RemoteException e)
		{
			Debug.log(e);
		}
	}
	
	public void removeClient(UserInterface user)
	{
		clients.remove(user.getId());
	}
	
	public void removeClient(int userId)
	{
		clients.remove(userId);
	}
	
	public Client getClient(model.Participant participant)
	{
		return getClient(participant.getPlayer());
	}
	
	public Client getClient(model.User user)
	{
		return getClient(user.getId());
	}
	
	public Client getClient(int userId)
	{
		return clients.get(userId);
	}
	
	public SessionInterface watchGame(int gameId, UserInterface user)
	{
		model.Game game = games.get(gameId);
		
		if (game != null)
		{
			game.watch((model.User) user);
		}
		
		return sessions.get(gameId);
	}
	
	public void joinGame(int gameId, PlayerInterface player, int seatIndex) throws GameException
	{
		model.Game game = games.get(gameId);
		
		if (game != null && player instanceof model.Player)
		{
			Participant participant = game.join((model.Player) player, seatIndex);
			
			Seat seat = new Seat();
			seat.setGameId(gameId);
			seat.setIndex(seatIndex);
			seat.setUsername(player.getUsername());
			seat.setPosition(participant.getPositionString());
			seat.setStack(participant.getStack());
			seat.setTaken(true);
			
			updateSeat(seat);
		}
	}

	public void leaveGame(int gameId, UserInterface user)
	{
		model.Game game = games.get(gameId);
		
		if (game != null)
		{
			game.leave((model.User) user);
		}
	}
	
	public ChosenAction chooseAction(model.Participant participant)
	{
		ChosenAction chosenAction = null;
		
		try
		{
			Client client = clients.get(participant.getUserId());
			
			chosenAction = client.chooseAction(participant.getGameId());
		}
		catch (RemoteException e)
		{
			Debug.log(e);
		}
		
		return chosenAction;
	}
	
	public void actionPerformed(model.Participant participant)
	{
		int gameId = participant.getGameId();
		
		int seatIndex = participant.getSeatIndex();
		
		Session session = sessions.get(gameId);
		
		SeatInterface seat = session.getSeat(participant.getSeatIndex());
		
		if (seat != null)
		{
			session.setTurnSeatIndex(seatIndex);
		}
		
		BetContainer betContainer = participant.getBetContainer();
		
		seat.setBet(betContainer.getBet());
		seat.setStack(betContainer.getChips());
		
		updateSeat(seat);
		
		if (seat != null)
		{
			model.Game game = games.get(gameId);
			
			for (model.User spectator: game.getSpectators())
			{
				Client client = clients.get(spectator.getId());
				
				try
				{
					client.setTurnSeatIndex(gameId, seatIndex);
				}
				catch (RemoteException e)
				{
					Debug.log(e);
				}
			}
		}
	}
	
	public void updateBoard(int gameId, ArrayList<model.Card> cards)
	{
		model.Game game = games.get(gameId);
		
		ArrayList<CardInterface> boardCards;
		
		if (cards != null)
		{
			boardCards = new ArrayList<CardInterface>(cards);
		}
		else
		{
			boardCards = new ArrayList<CardInterface>();
		}
		
		sessions.get(gameId).setBoardCards(boardCards);
		
		for (model.User spectator: game.getSpectators())
		{
			int userId = spectator.getUserId();
			
			try
			{
				clients.get(userId).updateBoard(gameId, boardCards);
			}
			catch (RemoteException e)
			{
				Debug.log(e);
			}
		}
	}
	
	public void updateHand(int gameId, model.Participant participant)
	{
		model.Game game = games.get(gameId);
		
		int userId = participant.getUserId();
		
		ArrayList<CardInterface> handCards = new ArrayList<CardInterface>(participant.getHand().getCards());
		
		SeatInterface seat = sessions.get(gameId).getSeat(participant.getSeatIndex());
		seat.setHandCards(handCards.size());
		
		for (model.User spectator: game.getSpectators())
		{
			Client client = clients.get(userId);
			
			try
			{
				if (spectator.getId() == userId)
				{
					client.updateHand(gameId, handCards);
				}
				else
				{
					updateSeat(seat);
				}
			}
			catch (RemoteException e)
			{
				Debug.log(e);
			}
		}
	}
	
	public void showGameRoundWinners(int gameId, ArrayList<model.Participant> winners)
	{
		Session session = sessions.get(gameId);
		
		ArrayList<SeatInterface> seats = new ArrayList<SeatInterface>();
		
		for (model.Participant winner: winners)
		{
			SeatInterface seat = session.getSeat(winner.getSeatIndex());
			seat.setWon(true);
			
			seats.add(seat);
		}
		
		updateSeats(seats);
	}
	
	public void updateSeat(SeatInterface seat)
	{
		int gameId = seat.getGameId();
		
		model.Game game = games.get(gameId);
		
		sessions.get(gameId).updateSeat(seat);
		
		for (model.User spectator: game.getSpectators())
		{
			Client client = clients.get(spectator.getId());
			
			try
			{
				client.updateSeat(seat);
			}
			catch (RemoteException e)
			{
				Debug.log(e);
			}
		}
	}

	public void updateSeats(ArrayList<SeatInterface> seats)
	{
		if (seats.size() > 0)
		{
			int gameId = seats.get(0).getGameId();
			
			model.Game game = games.get(gameId);
			
			Session session = sessions.get(gameId);
			
			for (SeatInterface seat: seats)
			{
				session.updateSeat(seat);
			}
			
			for (model.User spectator: game.getSpectators())
			{
				Client client = clients.get(spectator.getId());
				
				try
				{
					for (SeatInterface seat: seats)
					{
						client.updateSeat(seat);
					}
				}
				catch (RemoteException e)
				{
					Debug.log(e);
				}
			}
		}
	}
	
	public PlayerGameStatsInterface getPlayerStatistics(int gameId, PlayerInterface player)
	{
		model.Game game = games.get(gameId);
		
		model.Participant participant = game.getParticipant((model.Player) player);
		
		if (participant != null)
		{
			return participant.getGameStats();
		}
		
		return null;
	}
	
	public void sendChatMessage(int gameId, UserInterface user, String message)
	{
		model.Game game = games.get(gameId);
		
		for (model.User spectator: game.getSpectators())
		{
			Client client = clients.get(spectator.getId());
			
			try
			{
				client.sendChatMessage(gameId, user, message);
			}
			catch (RemoteException e)
			{
				Debug.log(e);
			}
		}
	}
	
	public void newGameRound(int gameId)
	{
		model.Game game = games.get(gameId);
		
		Session session = sessions.get(gameId);
		
		ArrayList<SeatInterface> seats = new ArrayList<SeatInterface>();
		
		for (model.Participant participant: game.getParticipants())
		{
			SeatInterface seat = session.getSeat(participant.getSeatIndex());
			seat.setDefault();
			
			seats.add(seat);
		}
		
		updateSeats(seats);
		
		updateBoard(gameId, null);
	}
}
